package sketch.specs;

import java.util.LinkedList;
import java.util.List;

import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.internal.core.dom.NaiveASTFlattener;

import sketch.ast.ASTUtils;
import sketch.util.Checker;
import sketch.util.SketchLog;

public class SymbolicValueReplacement {
	
	public final MethodDeclaration method;
	public final List<MethodInvocation> invokes;
	public final List<List<Integer>> ints;
	public final List<List<Double>> reals;
	
	public SymbolicValueReplacement(MethodDeclaration method, List<MethodInvocation> symbolicInvokes,
			List<List<Integer>> ints, List<List<Double>> reals) {
		this.method = method;
		this.invokes = symbolicInvokes;
		this.ints = ints;
		this.reals = reals;
		//only one could have content
		if(SymbolicFileParser.hasContent(ints)) {
			Checker.checkTrue(!SymbolicFileParser.hasContent(reals), "Only one value is supported for symbolic, if you have ints, no reals.");
		} else {
			Checker.checkTrue(SymbolicFileParser.hasContent(ints), "Only one value is supproted for symoblic, if you have ints, no reals");
		}
	}
	
	public List<MethodDeclaration> replaceValues() {
		List<MethodDeclaration> retMethods = new LinkedList<MethodDeclaration>();
		
		int count = 0;
		if(SymbolicFileParser.hasContent(ints)) {
		   //for ints
		  for(List<Integer> ins : ints) {
			MethodDeclaration replicate_method = ASTUtils.deepClone(method);
			SymbolicValueReplacementVisitor visitor = new SymbolicValueReplacementVisitor(this.invokes, ins);
			replicate_method.accept(visitor);
			replicate_method.getName().setIdentifier(this.method.getName().getIdentifier() + (count++));
			retMethods.add(replicate_method);
		  }
		} else if(SymbolicFileParser.hasContent(reals)) {
			//for reals
			for(List<Double> reallist : reals) {
				MethodDeclaration replicate_method = ASTUtils.deepClone(method);
				SymbolicValueReplacementVisitor visitor = new SymbolicValueReplacementVisitor(this.invokes, null, reallist);
				replicate_method.accept(visitor);
				replicate_method.getName().setIdentifier(this.method.getName().getIdentifier() + (count++));
				retMethods.add(replicate_method);
			  }
		} else {
			retMethods.add(ASTUtils.deepClone(method));
		}
		return retMethods;
	}
}

//replace method invocations with concrete values
class SymbolicValueReplacementVisitor extends NaiveASTFlattener {
	
	final List<MethodInvocation> invokes;
	final List<Integer> ints;
	final List<Double> reals;
	
	SymbolicValueReplacementVisitor(List<MethodInvocation> invokes, List<Integer> ints) {
		Checker.checkTrue(invokes.size() <= ints.size(), "The size of ints: " + ints.size()
				+ " should >= the size of invokes: " + invokes.size());
		Checker.checkTrue(invokes.size() == 1, "We current implementation supports only 1 symbolic value per method"); //also be aware of the following index = 0
		Checker.checkNull(ints, "The int list should not be null.");
		this.invokes = invokes;
		this.ints = ints;
		this.reals = null;
		checkValidity();
	}
	
	SymbolicValueReplacementVisitor(List<MethodInvocation> invokes, List<Integer> ints, List<Double> reals) {
		Checker.checkTrue(invokes.size() == 1, "We current implementation supports only 1 symbolic value per method"); //also be aware of the following index = 0
		this.invokes = invokes;
		this.ints = ints;
		this.reals = reals;
		checkValidity();
	}
	
	private void checkValidity() {
		if(ints != null) {
		    Checker.checkTrue(reals == null, "reals should be null.");	
		} else {
			Checker.checkTrue(reals != null, "reals should not be null.");		
		}
	}
	
	@Override
	public boolean visit(MethodInvocation method) {
		
		//SketchLog.logln("  visiting method: " + method.toString());
		
		int index = -1; 
		for(int i= 0; i < invokes.size(); i++) {
			MethodInvocation invoke = invokes.get(i);
			//SketchLog.logln("    -- invoke start position: " + invoke.getStartPosition() + ", method start position: " + method.getStartPosition());
			if(invoke.getStartPosition() == method.getStartPosition() ) {
				index = i;
				break;
			}
		}
		
		if(index != -1 && method.getParent() instanceof MethodInvocation) {
			if (this.ints != null) {
				/**
				 * note that int could represent int, boolean, long, short, byte, char
				 * */
				// do replacement
				int v = this.ints.get(index);
				// System.out.println(signature);
				
				Checker.checkTrue(method.getParent() instanceof MethodInvocation,
						"The symoblicInt() method is used in a wrong place."
								+ ", its parent is: " + method.getParent());
				MethodInvocation methodInvoke = (MethodInvocation) method.getParent();
				int indexOfArgument = methodInvoke.arguments().indexOf(method);
				Checker.checkTrue(indexOfArgument != -1,
						"The symoblicInt() should appear as one of its arguments.");

				List<Expression> expressions = methodInvoke.arguments();
				// replace the argument
				expressions.remove(indexOfArgument);
				Expression replacedExpr = null;
				if(methodInvoke.getName().equals("symbolicBoolean")) {
					replacedExpr = v == 1 ? method.getAST().newBooleanLiteral(true) : method.getAST().newBooleanLiteral(false);
				} else {
					String signature = methodInvoke.getName().toString();
					Expression numExpr = method.getAST().newNumberLiteral(v + "");
					if(signature.equals("symbolicLong")) {
						CastExpression castExpr = method.getAST().newCastExpression();
						castExpr.setExpression(numExpr);
						castExpr.setType(method.getAST().newSimpleType(method.getAST().newSimpleName("long")));
						replacedExpr = castExpr;
					} else if(signature.equals("symbolicShort")) {
						CastExpression castExpr = method.getAST().newCastExpression();
						castExpr.setExpression(numExpr);
						castExpr.setType(method.getAST().newSimpleType(method.getAST().newSimpleName("short")));
						replacedExpr = castExpr;
					} else if (signature.equals("symbolicByte")) {
						CastExpression castExpr = method.getAST().newCastExpression();
						castExpr.setExpression(numExpr);
						castExpr.setType(method.getAST().newSimpleType(method.getAST().newSimpleName("byte")));
						replacedExpr = castExpr;
					} else if (signature.equals("symbolicChar")) {
						CastExpression castExpr = method.getAST().newCastExpression();
						castExpr.setExpression(numExpr);
						castExpr.setType(method.getAST().newSimpleType(method.getAST().newSimpleName("char")));
						replacedExpr = castExpr;
					} else {
					    replacedExpr = method.getAST().newNumberLiteral(v + "");
					}
				}
				Checker.checkNull(replacedExpr, "The replaced expression should not be null");
				//Expression replacedInt = method.getAST().newNumberLiteral(v + "");
				expressions.add(indexOfArgument, replacedExpr);
			} else if (this.reals != null) {
				/**
				 * note that real coudl represent float, and double
				 * */
				
				int v = this.ints.get(index);
				// System.out.println(signature);
				
				Checker.checkTrue(method.getParent() instanceof MethodInvocation,
						"The symbolic float or double method is used in a wrong place."
								+ ", its parent is: " + method.getParent());
				MethodInvocation methodInvoke = (MethodInvocation) method.getParent();
				int indexOfArgument = methodInvoke.arguments().indexOf(method);
				Checker.checkTrue(indexOfArgument != -1,
						"The symbolic value declaration should appear as one of its arguments.");

				List<Expression> expressions = methodInvoke.arguments();
				// replace the argument
				expressions.remove(indexOfArgument);
				Expression replacedExpr = method.getAST().newNumberLiteral(v + "");
				expressions.add(indexOfArgument, replacedExpr);
			} else {
				Checker.fail("state error, bot ints and reals are null");
			}
		}
		
		return super.visit(method);
	}
}